#include <iostream>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <tmxlite/Map.hpp>
#include <tmxlite/Layer.hpp>
#include <tmxlite/TileLayer.hpp>
#include <tmxlite/ObjectGroup.hpp>

typedef int Gid;

// 先降低画面的刷新率
static const int MIN_FRAME_TIME = 100;
struct Text {

public:
    SDL_Texture* numbers; // 所有数字的集合
    int w;
    int h;

    Text(SDL_Texture*, int, int);
};

Text::Text(SDL_Texture* number, int w, int h) {

    this->numbers   = number;
    this->w         = w;
    this->h         = h;
}  

struct tile {

    // 当前点所在的图块集合
    SDL_Texture* sheet;
    // x coordinate in the world
    int x;
    // y coordinate in the world
    int y;
    // the x coordinate on the sprite sheet
    int tx;
    // the y coordinate on the sprite sheet
    int ty;

    int XCurrentFrame;  // 图块x方向的变化
    int YCurrentFrame;  // 图块y方向的变化

    // 图块的宽高
    int width;
    int height;

    // 当前点的标识
    Gid gid;

    tmx::Layer::Type gidType;
    std::string objectName;
    std::string objectClass;
    bool isVisible;     // 是否可见

    int  XMovingDirection;  // 0 不移动 1 向左 2 向右
    int  YMovingDirection;  // 0 不移动 1 向上 2 向下

    tile(SDL_Texture* tset, int x, int y, int tx, int ty, int w, int h, 
        Gid gid, tmx::Layer::Type gType, std::string oName, std::string cName);
};

tile::tile(SDL_Texture* tset, int x, int y, int tx, int ty, int w, int h, Gid gid,tmx::Layer::Type gType, std::string oName, std::string cName) 
: sheet(tset), x(x), y(y), tx(tx), ty(ty), width(w), height(h), gid(gid), gidType(gType), objectName(oName), objectClass(cName) {

    this->XCurrentFrame = 0;
    this->YCurrentFrame = 0;
    this->XMovingDirection = 0;
    this->YMovingDirection = 2;
    this->isVisible = true;
}

struct Block {

public:
    tile *tiles; // 当前块有的所有状态
    Block(tile *);
};

Block::Block(tile *tiles){

    this->tiles = tiles;
}

struct Hero {
    int blood;          // 血量
    int yellowKeynum;   // 黄钥匙数
    int blueKeynum;     // 蓝钥匙数
    int redKeynum;      // 红钥匙数
    int commonKeynum;   // 通用钥匙数
    int attack;         // 攻击数
    int defense;        // 防御
    int coinnum;        // 金币数

    int moveDirection;  // 移动方向 0 向下(默认) 1 向左 2 向右 3 向前
    bool isMoving;      // 角色是否在移动
    int  tiledIndex;    // 角色在加载图块集中的位置

    int  nextTiledLayer; // 移动后图块所在的层
    int  nextTildIndex;  // 移动后图块所在层的下标

    bool openingDoor;    // 是否正在打开门
    int  doorTiledIndex; // 门的坐标
};

struct Map {

public:
    std::vector<std::vector <Block *>> blocks; // 墙

    Hero *hero; // 角色信息

    int currentFollor;  // 当前的层数

    std::vector <Text *> text;  // 当前层数的文本

    int Draw(SDL_Renderer* renderer);   // 显示图块

    void DrawText(SDL_Renderer* renderer, int, int); // 显示文本

    int isCollision(SDL_Keycode keyCode);   // 碰撞检测

    void getGidPos(int moveX, int moveY);    // 获取移动之后块的坐标

    int rows;   // 每行的块数
    int cols;   // 每列的块数
    int blockWidth;
    int blockHeight;
};

Map *map = new Map();

enum HeroAttribute {
    BLOOD = 1,
    YELLO_KEY_NUM,
    BLUE_KEY_NUM,
    RED_KEY_NUM,
    COMMON_KEY_NUM,
    ATTACK,
    DEFENCE,
    COIN_NUM,
};

enum MoveDirection {
    MOVE_DOWN,
    MOVE_LEFT,
    MOVE_RIGHT,
    MOVE_UP,
};

enum TiledLayerType {
    BACKGROUND, // 背景
    BLOCKS,     // 道具层
    OBJECT,     // 对象层
};

int Map::Draw(SDL_Renderer* ren) {


    if (blocks[OBJECT][hero->doorTiledIndex]->tiles->YCurrentFrame == 4) {

        // 门打开之后,删除这个对象
        // blocks[OBJECT].erase(blocks[OBJECT].begin() + hero->doorTiledIndex);
        blocks[OBJECT][hero->doorTiledIndex]->tiles->isVisible = false;
    }

    // 遍历所有层
    for (int i = 0; i < blocks.size(); i++) {

        // 遍历层下的所有图块
        for (int j = 0; j < blocks[i].size(); j++) {

            auto pointTiled = blocks[i][j]->tiles;
            if (!pointTiled->isVisible) {
                continue;
            }

            // 地图上展示的位置
            SDL_Rect dest;
            dest.x = pointTiled->x;
            dest.y = pointTiled->y;
            dest.w = pointTiled->width;
            dest.h = pointTiled->height;

            SDL_Rect src;
            src.x = pointTiled->tx + (pointTiled->XCurrentFrame * 32);
            src.y = pointTiled->ty;
            if (pointTiled->gidType == tmx::Layer::Type::Object) {
                
                if (pointTiled->objectName == "hero") {
                    
                    if (hero->isMoving) {

                        pointTiled->XCurrentFrame = (pointTiled->XCurrentFrame + 1) % 4;
                        hero->isMoving = false;
                    }

                    // 人物根据移动方向来变换
                    src.y = pointTiled->ty + hero->moveDirection * pointTiled->height;

                } else if (pointTiled->objectClass == "monster") {

                    // npc 做个动画
                    pointTiled->XCurrentFrame = (pointTiled->XCurrentFrame + 1) % 4;

                    // npc 可以移动
                    // if (pointTiled->YMovingDirection == 1) {

                    //     pointTiled->y -= 4;
                    //     if (pointTiled->y == 0) {
                    //         pointTiled->YMovingDirection = 2; 
                    //     }
                    // } else if (pointTiled->YMovingDirection == 2) {
                        
                    //     pointTiled->y += 4;
                    //     if (pointTiled->y == (rows - 1) * blockHeight) {
                    //         pointTiled->YMovingDirection = 1; 
                    //     }
                    // }

                } else if (hero->openingDoor && (blocks[OBJECT][hero->doorTiledIndex]->tiles->x == pointTiled->x) &&
                    (blocks[OBJECT][hero->doorTiledIndex]->tiles->y == pointTiled->y)) {

                    // 找到对应的门
                    src.y += (pointTiled->YCurrentFrame * 32);

                    // 打开门的动画
                    if (++pointTiled->YCurrentFrame == 4) {

                        hero->openingDoor = false;
                    }
                }
            }

            src.w = pointTiled->width;
            src.h = pointTiled->height;

            SDL_RenderCopy(ren, pointTiled->sheet, &src, &dest);
        }
    }

    // 显示角色信息
    DrawText(ren, BLOOD, 11);
    DrawText(ren, YELLO_KEY_NUM, 1);
    DrawText(ren, BLUE_KEY_NUM, 2);
    DrawText(ren, RED_KEY_NUM, 3);
    DrawText(ren, COMMON_KEY_NUM, 4);
    DrawText(ren, ATTACK, 6);
    DrawText(ren, DEFENCE, 7);
    DrawText(ren, COIN_NUM, 8);

    return 0;
}

void Map::DrawText(SDL_Renderer* renderer, int arrributeType, int attributeYpos) {

    int attribute = 0;
    switch (arrributeType) {
    case BLOOD:
        attribute = hero->blood;
        break;
    case YELLO_KEY_NUM:
        attribute = hero->yellowKeynum;
        break;
    case BLUE_KEY_NUM:
        attribute = hero->blueKeynum;
        break;
    case RED_KEY_NUM:
        attribute = hero->redKeynum;
        break;
    case COMMON_KEY_NUM:
        attribute = hero->commonKeynum;
        break;
    case ATTACK:
        attribute = hero->attack;
        break;
    case DEFENCE:
        attribute = hero->defense;
        break;
    case COIN_NUM:
        attribute = hero->coinnum;
        break;
    }


    std::vector<int> attributePart;
    if (attribute == 0) {
       attributePart.push_back(0); 
    }

    // 把属性值拆分
    while (attribute > 0) {
        attributePart.push_back(attribute % 10);
        attribute /= 10;
    }

    int numberOffset = 0;
    for (int i = attributePart.size() - 1; i >= 0; i--) {
        
        // 字体缩小一倍显示
        SDL_Rect dest = {
            .x = (cols - 2) * blockWidth + numberOffset,
            .y = attributeYpos * blockHeight + blockHeight / 4,
            .w = text[attributePart[i]]->w / 2,
            .h = text[attributePart[i]]->h / 2,
        };
        numberOffset += text[i]->w / 2;

        SDL_RenderCopy(renderer, text[attributePart[i]]->numbers, NULL, &dest);
    }
}

void Map::getGidPos(int moveX, int moveY) {

    // 判断和道具层的碰撞
    for (int i = 0; i < blocks[BLOCKS].size(); i++) {

        // 移动之后的坐标和地图上已有快的坐标重合
        if (blocks[BLOCKS][i]->tiles->x == moveX && blocks[BLOCKS][i]->tiles->y == moveY) {
            
            hero->nextTiledLayer = BLOCKS;
            hero->nextTildIndex = i;
            return ;
        }
    }

    // 判断和对象层的碰撞
    for (int i = 0; i < blocks[OBJECT].size(); i++) {

        // 移动之后的坐标和地图上已有快的坐标重合
        if (blocks[OBJECT][i]->tiles->x == moveX && blocks[OBJECT][i]->tiles->y == moveY) {

            hero->nextTiledLayer = OBJECT;
            hero->nextTildIndex = i;
            return ;
        }
    }
    
    hero->nextTiledLayer = BACKGROUND;
    return ;
}

enum tiedGid {
    YELLOW_KEY = 1,
    BLUE_KEY = 2,
    RED_KEY = 3,
    COMMON_KEY = 4,
    BLUE_DIAMOND = 7,
    RED_DIAMOND = 8,
    RED_LIQUID_MEDICINE = 11,
    GROUND = 29,
    WALL = 30,
    YELLOW_DOOR = 37,
    BLUE_DOOR = 38,
    RED_DOOR = 39,
    SPECIAL_DOOR = 40,
};

int Map::isCollision(SDL_Keycode keyCode) {

    // 获取角色移动的位置
    auto heroTiled = blocks[OBJECT][hero->tiledIndex]->tiles;
    int moveNextXPos = heroTiled->x, moveNextYPos = heroTiled->y;

    switch (keyCode)
    {
    case SDLK_UP:

        moveNextYPos = heroTiled->y - 32;
        hero->moveDirection = MOVE_UP;
        hero->isMoving = true;
        break;
    case SDLK_DOWN:

        moveNextYPos = heroTiled->y + 32;
        hero->moveDirection = MOVE_DOWN;
        hero->isMoving = true;
        break;
    case SDLK_LEFT:

        moveNextXPos = heroTiled->x - 32;
        hero->moveDirection = MOVE_LEFT;
        hero->isMoving = true;
        break;
    case SDLK_RIGHT:

        moveNextXPos = heroTiled->x + 32;
        hero->moveDirection = MOVE_RIGHT;
        hero->isMoving = true;
        break;
    default:
        break;
    }

    // 获取移动之后点的信息
    getGidPos(moveNextXPos, moveNextYPos);
    
    // 角色是否允许到下一个格
    bool canMove = true;
    Gid nextGid = blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->gid;
    if ( nextGid != WALL) {

        // 门
        if (nextGid == YELLOW_DOOR) {

            if (hero->yellowKeynum > 0 || hero->commonKeynum > 0) {

                hero->yellowKeynum > 0 ? hero->yellowKeynum-- : hero->commonKeynum--;
                hero->openingDoor = true;
                hero->doorTiledIndex = hero->nextTildIndex;
            } else
                canMove = false;
        }

        if (nextGid == BLUE_DOOR) {

            if (hero->blueKeynum > 0 || hero->commonKeynum > 0) {

                hero->blueKeynum > 0 ? hero->blueKeynum-- : hero->commonKeynum--;
                hero->openingDoor = true;
                hero->doorTiledIndex = hero->nextTildIndex;
            } else
                canMove = false;
        }

        if (nextGid == RED_DOOR) {

            if (hero->redKeynum > 0 || hero->commonKeynum > 0) {

                hero->redKeynum > 0 ? hero->redKeynum-- : hero->commonKeynum--;
                hero->openingDoor = true;
                hero->doorTiledIndex = hero->nextTildIndex;
            } else
                canMove = false;
        }

        if (nextGid == SPECIAL_DOOR) {

            if (hero->commonKeynum > 0) {

                hero->commonKeynum--;
                hero->openingDoor = true;
                hero->doorTiledIndex = hero->nextTildIndex;
            } else
                canMove = false;
        }

        // npc
        if (blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->objectClass == "monster") {

            if (hero->blood > 0) {

                hero->blood -= 100;
                hero->coinnum += 5;
                blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->isVisible = false;
            } else
               canMove = false;
        }

        // 黄钥匙打开黄色的门
        if (nextGid == YELLOW_KEY) {

            hero->yellowKeynum++;
            blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->isVisible = false;
        }

        // 蓝钥匙打开蓝色的门
        if (nextGid == BLUE_KEY) {

            hero->blueKeynum++;
            blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->isVisible = false;
        }

        // 红钥匙打开红色的门
        if (nextGid == RED_KEY) {

            hero->redKeynum++;
            blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->isVisible = false;
        }

        // 万能钥匙打开任意颜色的门
        if (nextGid == COMMON_KEY) {

            hero->commonKeynum++;
            blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->isVisible = false;
        }

        // 红药水加生命
        if (nextGid == RED_LIQUID_MEDICINE) {

            hero->blood+= 100;
            blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->isVisible = false;
        }

        // 红宝石加攻击
        if (nextGid == RED_DIAMOND) {

            hero->attack+= 10;
            blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->isVisible = false;
        }

        // 蓝宝石加防御
        if (nextGid == BLUE_DIAMOND) {

            hero->defense+= 10;
            blocks[hero->nextTiledLayer][hero->nextTildIndex]->tiles->isVisible = false;
        }

        // 不是墙的位置,而且角色没有超出边界(x轴有三列是角色信息展示)
        if (canMove && moveNextXPos >= 0 && moveNextXPos <= (cols - 1 - 3) * blockWidth && 
            moveNextYPos >= 0 && moveNextYPos <= (rows - 1) * blockHeight) {
            
            switch (hero->moveDirection)
            {
            case MOVE_UP:
                heroTiled->y -= 32;
                break;
            case MOVE_DOWN:
                heroTiled->y += 32;
                break;
            case MOVE_LEFT:
                heroTiled->x -= 32;
                break;
            case MOVE_RIGHT:
                heroTiled->x += 32;
                break;
            default:
                break;
            }
        }
    }

    return 0;
}

// g++ tiledMap.cpp -lSDL2 -lSDL2_image -ltmxlite -std=c++11
int main() {

    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        std::cerr << "SDL initialization failed: " << SDL_GetError() << std::endl;
        return 1;
    }

    tmx::Map tiled_map;
    tiled_map.load("./tiled/magicTower.tmx");

    // We need to know the size of the map (in tiles)
    auto map_dimensions = tiled_map.getTileCount();
    map->rows = map_dimensions.y;
    map->cols = map_dimensions.x;

    // We also need to know the dimensions of the tiles.
    auto tilesize = tiled_map.getTileSize();
    map->blockWidth = tilesize.x;
    map->blockHeight = tilesize.y;

      // Create SDL window and renderer
    SDL_Window* window = SDL_CreateWindow("Magic Tower",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, map->cols * map->blockWidth, map->rows *  map->blockHeight,  SDL_WINDOW_SHOWN);
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {

        SDL_Log("IMG_Init: %s", IMG_GetError());
        return 2;
    }

    map->hero = new Hero{
        .blood          = 100,
        .yellowKeynum   = 1,
        .blueKeynum     = 2,
        .redKeynum      = 3,
        .commonKeynum   = 4,
        .attack         = 56,
        .defense        = 789,
        .coinnum        = 99,
        .moveDirection  = 0,
    };

    auto& map_tilesets = tiled_map.getTilesets();
    std::map<Gid, SDL_Texture*> tilesets;
    std::map<Gid, Gid> gidRange;
    for (auto& tset : map_tilesets) {
        
        SDL_Surface *tilesetsSurface = IMG_Load(tset.getImagePath().c_str());
        if (tilesetsSurface == nullptr) {

            SDL_Log("Failed to load map.png: %s", IMG_GetError());
            return 2;
        }

        auto tex = SDL_CreateTextureFromSurface(renderer, tilesetsSurface);
        if (!tex) {
            std::cerr << "ERROR: failed to create texture: " 
                    << SDL_GetError() << "\n";
        }

        tilesets.insert(std::pair<Gid, SDL_Texture*>(tset.getFirstGID(), tex));
        gidRange.insert(std::pair<Gid, Gid>(tset.getFirstGID(), tset.getLastGID()));
    }

    // 加载所有的图层
    auto &map_layers = tiled_map.getLayers();
    map->blocks.resize(map_layers.size());

    for (int layerIndex = 0; layerIndex < map_layers.size(); layerIndex++) {

        // We're only looking to render the tiles on the map, so if
        // this layer isn't a tile layer, we'll move on.
        tmx::Layer::Type layerType = map_layers[layerIndex]->getType();
        if (layerType == tmx::Layer::Type::Tile) {
        
            auto *tile_layer = dynamic_cast<const tmx::TileLayer*>(map_layers[layerIndex].get());

            // 图层里面的所有点
            auto& layer_tiles = tile_layer->getTiles();
            for (auto y = 0; y < map->rows; ++y) {
                
                for (auto x = 0; x < map->cols; ++x) {

                    // 图块横向展示
                    auto tile_index = x + (y * map->cols);
                    // Grab the GID of the tile we're at.
                    // layer_tiles 把二维数组放到容器里面
                    auto cur_gid = layer_tiles[tile_index].ID;

                    // 当前点没有图块
                    if (cur_gid == 0) {
                        continue;
                    }

                    // Find the tileset that this tile belongs to. The tileset
                    // whose first GID is less-than-or-equal to this GID, is
                    // the tileset our GID belongs to. A tileset with a first
                    // GID that is greater than this tile means that this tile
                    // does not belong to that tileset.
                    auto tset_gid = -1;
                    for (auto& ts : tilesets) {

                        // 判断当前图块属于的图块集
                        if (cur_gid >= ts.first && cur_gid <= gidRange[ts.first]) {

                            tset_gid = ts.first;
                            break;
                        }
                    }

                    auto ts_width = 0;
                    auto ts_height = 0;
                    SDL_QueryTexture(tilesets[tset_gid], NULL, NULL, &ts_width, &ts_height);
                    
                    // 当前图块在素材中坐标
                    // ts_width / tile_width => x 方向有多少点
                    auto region_x = ((cur_gid - tset_gid) % (ts_width / map->blockWidth)) * map->blockWidth;
                    auto region_y = ((cur_gid - tset_gid) / (ts_width / map->blockWidth)) * map->blockHeight;

                    // Calculate the world position of our tile. This is easy,
                    // because we're using nested for-loop to visit each x,y
                    // coordinate.
                    // 图块在地图中的位置
                    auto x_pos = x * map->blockWidth;
                    auto y_pos = y * map->blockHeight;

                    // Phew, all done.
                    map->blocks[layerIndex].push_back(new Block(new tile(tilesets[tset_gid], x_pos, y_pos, 
                        region_x, region_y, map->blockWidth, map->blockHeight, cur_gid, layerType, "", "")));
                }
            }
        }

        if (layerType == tmx::Layer::Type::Object) {

            auto *tile_object = dynamic_cast<const tmx::ObjectGroup*>(map_layers[layerIndex].get());
            auto objects = tile_object->getObjects();

            for (int i = 0; i < objects.size(); i++) {

                auto cur_gid = objects[i].getTileID();
                auto tset_gid = -1;
                for (auto& ts : tilesets) {

                    // 判断当前图块属于的图块集
                    if (cur_gid >= ts.first && cur_gid <= gidRange[ts.first]) {

                        tset_gid = ts.first;
                        break;
                    }
                }

                auto ts_width = 0;
                auto ts_height = 0;
                SDL_QueryTexture(tilesets[tset_gid], NULL, NULL, &ts_width, &ts_height);

                auto region_x = ((cur_gid - tset_gid) % (ts_width / map->blockWidth)) * map->blockWidth;
                auto region_y = ((cur_gid - tset_gid) / (ts_width / map->blockWidth)) * map->blockHeight;

                if (objects[i].getName() == "hero") {
                    // 记录主角的位置
                    map->hero->tiledIndex = map->blocks[layerIndex].size();
                }
                
                // 不知道为什么,object的y坐标总数错一个位    
                map->blocks[layerIndex].push_back(new Block(new tile(tilesets[tset_gid], objects[i].getPosition().x, objects[i].getPosition().y - map->blockHeight, 
                    region_x, region_y, map->blockWidth, map->blockHeight, cur_gid, layerType, objects[i].getName(), objects[i].getClass())));
            }
        }
    }

    SDL_Surface *tilesetsSurface = IMG_Load("tiled/number.png");
    if (tilesetsSurface == nullptr) {

        SDL_Log("Failed to load map.png: %s", IMG_GetError());
        return 2;
    }

    SDL_Texture *number = nullptr;
    SDL_Rect numberPos = { 0, 0, 0, 0 };
    for (int i = 0; i < 10; i++) {

        // 每个数字的位置
        numberPos.x = i * (tilesetsSurface->w / 10);
        numberPos.h = tilesetsSurface->h;
        numberPos.w = tilesetsSurface->w / 10;

         // 创建指定尺寸
        SDL_Surface* subSurface = SDL_CreateRGBSurface(0, numberPos.w, numberPos.h , tilesetsSurface->format->BitsPerPixel,
                                                       tilesetsSurface->format->Rmask, tilesetsSurface->format->Gmask,
                                                       tilesetsSurface->format->Bmask, tilesetsSurface->format->Amask);
        if (subSurface == nullptr) {
            SDL_Log("Failed to create surface: %s", SDL_GetError());
            return -1;
        }

        // 复制一部分到新的表面
        SDL_BlitSurface(tilesetsSurface, &numberPos, subSurface, nullptr);

        // 创建纹理
        SDL_Texture *number = SDL_CreateTextureFromSurface(renderer, subSurface);
        if (number == nullptr) {
            SDL_Log("Failed to create texture: %s", SDL_GetError());
            SDL_FreeSurface(subSurface);
            return -2;
        }

        map->text.push_back(new Text(number, numberPos.w, numberPos.h));
    }

    // Main loop
    bool quit = false;
    SDL_Event event;
    Uint32 frameTime = 0;
    while (!quit) {

        while (SDL_PollEvent(&event) != 0) {
            if (event.type == SDL_QUIT) {
                quit = true;
            } else if (event.type == SDL_KEYDOWN) {

                map->isCollision(event.key.keysym.sym);
            }
        }

        // Render loop
        SDL_RenderClear(renderer);

        frameTime = SDL_GetTicks();

        map->Draw(renderer);

        SDL_RenderPresent(renderer);

        if(SDL_GetTicks() - frameTime < MIN_FRAME_TIME) {
			SDL_Delay(MIN_FRAME_TIME - (SDL_GetTicks () - frameTime));
		}
    }

    // Cleanup
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}
